import SpeechRecognizer from './conn-service'
import WebRecorder from './webrecorder'
import { isEmptyQuote } from '@/utils'
import { EventNames, ErrorReport } from './index.d'

export default class ASR {
  config: LooseObject = {}
  recognizer: SpeechRecognizer
  recorder: WebRecorder

  results: LooseObject[] = []
  processText = ''

  private statusTacks: EventNames[] = []
  private tag = '[::ASR::]'
  private receivedGapTolerance = 5000

  constructor(config?: LooseObject) {
    this.config = Object.assign({}, config)
  }

  private log(...messages) {
    console.log(this.tag, ...messages)
  }

  private error(...messages) {
    console.error(this.tag, ...messages)
  }

  private matchNativeError(error: DOMException) {
    let message: string
    const flat: string | number = error.name || error.code
    switch (flat) {
      case 'PERMISSION_DENIED':
      case 'PermissionDeniedError':
        message = '未获取到麦克风权限'
        break
      case 'NOT_SUPPORTED_ERROR':
      case 'NotSupportedError':
      case 'MANDATORY_UNSATISFIED_ERROR':
      case 'MandatoryUnsatisfiedError':
      case 'NotFoundError':
        message = '您的设备不支持录音'
        break
      default:
        message = '无法打开麦克风, 请重试'
        break
    }
    return message
  }

  private onChangeState(eventName: EventNames, event?: LooseObject) {
    const tacksLength: number = this.statusTacks.length
    if (!tacksLength || this.statusTacks[tacksLength - 1] !== eventName) {
      this.statusTacks.push(eventName)
    }
    const tag = `[onChangeState:${eventName}]`
    this.config.on?.onChangeState?.(eventName, event)
    this.config.on?.[eventName]?.(event)
    if (eventName === EventNames.onRecognitionResultChange) return
    if ([EventNames.OnReceivedData, EventNames.onRecognitionResultChange].includes(eventName)) return
    if (eventName === EventNames.onError) {
      this.error(tag, event)
    } else {
      this.log(tag, event)
    }
  }

  /**
   * 是否存在某种状态
   * 如果检测状态为错误状态`onError`，则直接检测是否包含
   * 否则先检测是否包含错误状态，如果包含了错误状态则默认所有状态失效
   * EG: onError会掐断录音机、socket连接
   * @param eventName
   * @returns
   */
  private experienced(eventName: EventNames) {
    const has = this.statusTacks.includes(eventName)
    if (eventName === EventNames.onError) return has
    return !this.statusTacks.includes(EventNames.onError) && has
  }

  private onError(error?: ErrorReport | DOMException, type?: string) {
    if (this.experienced(EventNames.onStop)) return
    if (typeof error === 'string') {
      error = {
        message: error,
      }
    }
    if (type === 'native') {
      error = {
        ...error,
        message: this.matchNativeError(error as DOMException) || error.message,
      }
    }
    this.onChangeState(EventNames.onError, error)
    // this.closeInterval()
    this.stop()
  }

  private recognizerIsActive() {
    return (
      this.experienced(EventNames.onRecognitionStart) &&
      !this.experienced(EventNames.onRecognitionComplete) &&
      !this.experienced(EventNames.onError)
    )
  }

  private async getRecorder() {
    return new Promise((resolve, reject) => {
      this.recorder = new WebRecorder()
      if (isEmptyQuote(this.recorder)) {
        return reject({ toast: true, message: '语音识别还未准备好' })
      }
      this.recorder.OnReceivedData = (res: LooseObject) => {
        resolve(this.recorder)
        if (!this.recognizerIsActive()) return
        this.recognizer?.sendBuffer(res)
        this.onChangeState(EventNames.OnReceivedData, res)
      }
      this.recorder.OnError = (error: LooseObject) => {
        this.error('[recorder.OnError]', error)
        /**
         * 如果已获取录音权限，依然抛出`NotAllowedError`
         * 则为Safari禁止自动模仿音频错误
         * 可忽略
         */
        if (this.experienced(EventNames.OnReceivedData) && error?.name === 'NotAllowedError') return
        this.recorder.stop()
        this.error('[recorder.OnError]', error)
        this.onError(error, 'native')
      }
      this.recorder
        ?.start?.()
        .then(resolve)
        .catch((message: string) => {
          this.onError({
            message
          })
        })
    })
  }

  private async getRecognizer() {
    return new Promise((resolve, reject) => {
      this.recognizer = new SpeechRecognizer(this.config)
      if (isEmptyQuote(this.recognizer)) {
        return reject({ toast: true, message: '语音识别还未准备好' })
      }
      const timeout = setTimeout(() => {
        if (!this.statusTacks?.length) {
          const error = {
            message: '网络连接超时'
          }
          this.onError(error)
          reject(error)
        }
        clearTimeout(timeout)
      }, this.receivedGapTolerance)
      this.recognizer.onRecStart = (res) => {
        clearTimeout(timeout)
        resolve(this.recognizer)
        if (this.experienced(EventNames.onStop)) return
        // this.log('[recognizer.OnRecognitionStart]', res)
        this.onChangeState(EventNames.onRecognitionStart, res)
      }
      this.recognizer.onSentenceChange = (res: LooseObject) => {
        // this.log('[recognizer.OnRecognitionResultChange]', res)
        const text = `${res.sentence || ''}`.trim()
        this.processText = `${this.reviewResultText(false)}${text}`
        this.onChangeState(EventNames.onRecognitionResultChange, res)
      }
      this.recognizer.onSentenceEnd = (res: LooseObject) => {
        // this.log('[recognizer.OnSentenceEnd]', res)
        this.results.push(res)
        this.processText = this.reviewResultText()
        this.onChangeState(EventNames.onSentenceEnd, res)
      }
      this.recognizer.onRecComplete = (res: LooseObject) => {
        // this.log('[recognizer.OnRecognitionComplete]', res)
        this.onChangeState(EventNames.onRecognitionComplete, res)
      }
      this.recognizer.onError = (error) => {
        this.error('[recognizer.OnError]', error)
        this.onError(error)
      }
      this.recognizer?.setup?.()
    })
  }

  async start() {
    return this.getRecorder()
      .then(() => this.getRecognizer())
  }

  async stop() {
    if (this.experienced(EventNames.onStop)) return
    this.log('[stop]')
    try {
      if (this.recognizer) {
        if (this.experienced(EventNames.onRecognitionStart)) {
          this.recognizer.stop?.()
        }
      }
      this.recorder?.stop()
      this.config.on?.onStoped?.()
      return Promise.resolve()
    } catch (error: unknown) {
      this.error(error)
      return Promise.reject(error)
    } finally {
      this.onChangeState(EventNames.onStop)
    }
  }

  reviewResultText(useDynamic = true) {
    return (
      this.results?.map?.((result) => result?.sentence).join('') ||
      (!useDynamic ? '' : this.processText)
    )
  }
}
